﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using CoreLibrary;
using CoreLibrary.Repo;
using Entities.DataBase.Obj;
using Entities.DataBase.Persistent;

namespace Entities.DataBase.Impl {

    public class ProductRepository : BaseRepository<Product>, IProductRepository {

        private static readonly Func<DataContext, string, Product>
          LinqGetProduct = CompiledQuery.Compile((DataContext context, string id)
              => context.GetTable<Product>().SingleOrDefault(o => o.ProductId.Equals(id)));

        private static readonly Func<DataContext, int, int, int, IQueryable<Product>>
          LinqGetProductsByCatalogId = CompiledQuery.Compile((DataContext context, int catId, int offset, int limit)
              => context.GetTable<Product>().Where(p => p.CatalogId == catId).OrderByDescending(p => p.DateCreated).Skip(offset).Take(limit));

        private static readonly Func<DataContext, int, int,IQueryable<Product>>
          LinqGetAllProductsByCatalogId = CompiledQuery.Compile((DataContext context, int lid, int rid)
              => (
                  from category in context.GetTable<ProductCatalog>().Where(c => c.LID > lid && c.LID < rid)
                  join product  in context.GetTable<Product>().OrderByDescending(p => p.DateCreated)
                  on category.Id equals product.CatalogId
                  select product
          )
        );

        private static readonly Func<DataContext, string, IQueryable<Product>>
          LinqGetProductsByKeyword = CompiledQuery.Compile((DataContext context, string keyword)
              => context.GetTable<Product>().Where(p => "".Equals(keyword) || p.Name.Contains(keyword)
              || p.CName.Contains(keyword)|| p.Description.Contains(keyword)||p.Keywords.Contains(keyword))
              .OrderByDescending(p => p.DateCreated)
        );

        public override string TableName {
            get {
                return "D_PRODUCT";
            }
        }


        public void Add(Product product) {
            Execute((context, transaction) => {
                context.GetTable<Product>().InsertOnSubmit(product);
                context.SubmitChanges();
                transaction.Complete();
            });
        }

        public List<Product> List(string keyword) {
            return Execute(context => LinqGetProductsByKeyword(context, Util.GetString(keyword)).ToList());
        }

        

        public void Update(string productId, ProductUpdator updator) {
            Execute((context, ts) => {
                var product = LinqGetProduct(context, productId);
                if (product == null) {
                    throw new Exception("Product do not exist.");
                }

                updator.UpdateProduct(product);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public Product GetProduct(string productId) {
            return Execute(context => LinqGetProduct(context, productId));
        }

        public string UpdateLogo(string productId, string path) {
            return Execute((context, ts) => {
                var product = LinqGetProduct(context, productId);
                if (product == null) {
                    throw new Exception("Product do not exist.");
                }

                if (!string.IsNullOrEmpty(path) && !product.Thumbs.Equals(path)) {
                    product.Thumbs = path;
                    product.DateUpdated = DateTime.UtcNow;
                }

                context.SubmitChanges();
                ts.Complete();
                return path;
            });
        }
        
        public void Remove(string productId) {
            Execute((context, transaction) => {

                var product = LinqGetProduct(context, productId);
                if (product == null) {
                    transaction.Complete();
                    return;
                }

                var records = context.GetTable<PurchaseRecord>().Where(r => r.ProductId.Equals(product.ProductId)).ToList();
                if (!CollectionUtil.IsEmpty(records)) {
                     context.GetTable<PurchaseRecord>().DeleteAllOnSubmit(records);
                }

                context.GetTable<Product>().DeleteOnSubmit(product);
                context.SubmitChanges();
                transaction.Complete();
            });
        }

        public List<Product> List(int catId, int offset, int limit) {
            return Execute(context => {
                var category = context.GetTable<ProductCatalog>().SingleOrDefault(c => c.Id == catId);
                if (category == null) {
                    return new List<Product>();
                }

                if (category.IsLeaf) {
                    return LinqGetProductsByCatalogId(context, catId, offset, limit).ToList();
                }

                var products = LinqGetAllProductsByCatalogId(context, category.LID, category.RID);
                return products.Skip(offset)
                               .Take(limit)
                               .ToList();
            });
        }

        /*public void CreatePurchaseRecord(PurchaseRecord record) {
            Execute((context, transactionScope) => {
                var productStock = LinqGetPurchaseRecordByLocProductId(context, record.ProductId, record.Location);
                if (productStock == null) {
                    context.GetTable<Stock>().InsertOnSubmit(new Stock {
                        ProductId = record.ProductId,
                        Location  = record.Location,
                        Count     = record.Count,
                    });
                } 
                else {
                    productStock.Stock = productStock.Stock + record.Count;
                    productStock.DateUpdated = DateTime.UtcNow;
                }

                context.GetTable<PurchaseRecord>().InsertOnSubmit(record);
                context.SubmitChanges();
                transactionScope.Complete();
            });
        }

        public string TransferOutPurchaseStock(string recordId, int location, int count, double postage) {
            return Execute((context, ts) => {
                var guid = GuidUtil.GuidN();
                var stock = LinqGetPurchaseRecord(context, recordId);
                if (stock != null) {
                    stock.Count = Math.Max(stock.Count - count, 0);
                    stock.Stock = Math.Max(stock.Stock - count, 0);
                    stock.DateUpdated = DateTime.UtcNow;
                    context.GetTable<PurchaseRecord>().InsertOnSubmit(new PurchaseRecord {
                        Id           = guid,
                        OperatorId   = stock.OperatorId,
                        ProductId    = stock.ProductId,
                        Cost         = stock.Cost,
                        Postage      = (decimal)postage,
                        CostCurrency = stock.CostCurrency,
                        Count        = count,
                        Stock        = count,
                        Location     = location,
                    });
                }
                context.SubmitChanges();
                ts.Complete();
                return guid;
            });
        }

        public void TransferAllPurchaseStock(string recordId, int location, double postage) {
            Execute((context, transactionScope) => {
                var stock = LinqGetPurchaseRecord(context,recordId);
                if (stock != null) {
                    stock.Postage  = (decimal) postage;
                    stock.Location = location;
                    stock.DateUpdated = DateTime.UtcNow;
                }

                context.SubmitChanges();
                transactionScope.Complete();
            });
        }

        public void UpdatePurchaseRecord(string recordId, PurchaseRecordUpdator recordUpdator) {
            Execute((context, ts) => {
                var record = LinqGetPurchaseRecord(context, recordId);
                if (record == null) {
                    throw new Exception("Product Record doesn't exist.");
                }

                recordUpdator.Update(record);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void RemovePurchaseRecord(string stockId) {
            Execute((context, ts) => {
                var record = LinqGetPurchaseRecord(context, stockId);
                if (record == null) {
                    throw new Exception("ProductStock doesn't exist.");
                }

                var productStock = LinqGetPurchaseRecordByLocProductId(context, record.ProductId, record.Location);
                if (productStock != null) {
                    productStock.DateUpdated = DateTime.UtcNow;
                    productStock.Stock = Math.Max(productStock.Stock - record.Count, 0);
                    context.SubmitChanges();
                }
                context.GetTable<PurchaseRecord>().DeleteOnSubmit(record);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void DecreasePurchaseStocks(DecreasePurchaseStockReq req) {
            Execute((context, ts) => {
                foreach (var recordId in req.Parameters.Keys) {
                    var record = LinqGetPurchaseRecord(context, recordId);
                    if (record == null) {
                        continue;
                    }
                    var stock = req.Parameters[recordId];
                    record.Stock = Math.Max(record.Stock - stock, 0);
                    record.DateUpdated = DateTime.UtcNow;
                }
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public Stock GetStock(string productId, int location) {
            return Execute(context => LinqGetPurchaseRecordByLocProductId(context, productId, location));
        }

        public List<StockInfo> GetStocksByLocation(int location) {
            return GetAllStocks().Where(s => s.Location == location).ToList();
        }

        public List<StockInfo> GetAllStocks() {
            return Execute(context => {
                var records = LinqGetAllPurchaseRecords(context);
                return records.GroupBy(r => new { r.ProductId, r.CName, r.Location })
                              .Select(g  => new StockInfo {
                                ProductId   = g.Key.ProductId,
                                ProductName = g.Key.CName,
                                Stock       = g.Sum(l => l.Stock),
                                Location    = g.Key.Location,
                              })
                              .OrderBy(s => s.ProductName)
                              .ToList();
            });
        }

       

        public void InsertOrUpdateStock(string productId, int location, int stock) {
             Execute((context, ts) => {
                 var productStock = LinqGetPurchaseRecordByLocProductId(context, productId, location);
                 if (productStock == null) {
                     context.GetTable<Stock>().InsertOnSubmit(new Stock {
                         ProductId = productId,
                         Location  = location,
                         Stock     = stock,
                     });
                 }
                 else {
                     productStock.Stock = productStock.Stock + stock;
                     productStock.DateUpdated = DateTime.UtcNow;
                 }
                 context.SubmitChanges();
                 ts.Complete();
             });
        }

        public void DecreaseStocks(DecreaseStocksReq req) {
            Execute((context, ts) => {
                req.Parameters.ForEach(item => {
                    var stock   = LinqGetPurchaseRecordByLocProductId(context, item.Key.ProductId, item.Key.Location);
                    stock.Stock = Math.Max(stock.Stock - item.Value, 0);
                });
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void UpdateStock(string productId, int location, int stock) {
            Execute((context, ts) => {
                var productStock  = LinqGetPurchaseRecordByLocProductId(context, productId, location);
                if (productStock != null) {
                    productStock.Stock = stock;
                    productStock.DateUpdated = DateTime.UtcNow;
                    context.SubmitChanges();
                }
                ts.Complete();
            });
        }*/

    }
}